/*
 * <one line to give the program's name and a brief idea of what it does.>
 * Copyright (C)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "arena.hpp"

#include "shipai.hpp"
#include "shipplayer.hpp"
#include "shipdrone.hpp"

Arena::Arena(const PlayerData * data) : m_Data(data) {
	// player drones
	m_Drones[0] = new ShipDrone(data->droneTypes[0], data->drones[0]);
	m_Drones[1] = new ShipDrone(data->droneTypes[1], data->drones[1]);
	m_Drones[2] = new ShipDrone(data->droneTypes[2], data->drones[2]);
	m_Drones[3] = new ShipDrone(data->droneTypes[3], data->drones[3]);
	m_Drones[4] = new ShipDrone(data->droneTypes[4], data->drones[4]);

	// player ships
	m_ShipLeft = new ShipPlayer(data->buildTest.data[0], data->buildTest.data[1], data->buildTest.data[2], data->buildTest.data[3]);
	m_ShipMiddle = new ShipPlayer(data->buildTest.data[4], data->buildTest.data[5], data->buildTest.data[6], data->buildTest.data[7]);
	m_ShipRight = new ShipPlayer(data->buildTest.data[8], data->buildTest.data[9], data->buildTest.data[10], data->buildTest.data[11]);

	// ai ship
	m_ShipAI = new ShipAI(data->aiTest);
}

Arena::~Arena() {
	delete m_ShipAI;
	delete m_ShipLeft;
	delete m_ShipMiddle;
	delete m_ShipRight;

	for(auto i = 0; i < 5; ++i) {
		delete m_Drones[i];
	}
}

void Arena::prepare() {
	// NOTE we must re-read our player data
	// ai data
	m_ShipAI->setLevel(m_Data->aiTest);
	m_ShipAI->reset();

	// left ship
	m_ShipLeft->setShipPartLevel(upgradeType::WEAPON, m_Data->buildTest.data[0]);
	m_ShipLeft->setShipPartLevel(upgradeType::REACTOR, m_Data->buildTest.data[1]);
	m_ShipLeft->setShipPartLevel(upgradeType::HULL, m_Data->buildTest.data[2]);
	m_ShipLeft->setShipPartLevel(upgradeType::WINGS, m_Data->buildTest.data[3]);
	m_ShipLeft->reset();

	// middle ship
	m_ShipMiddle->setShipPartLevel(upgradeType::WEAPON, m_Data->buildTest.data[4]);
	m_ShipMiddle->setShipPartLevel(upgradeType::REACTOR, m_Data->buildTest.data[5]);
	m_ShipMiddle->setShipPartLevel(upgradeType::HULL, m_Data->buildTest.data[6]);
	m_ShipMiddle->setShipPartLevel(upgradeType::WINGS, m_Data->buildTest.data[7]);
	m_ShipMiddle->reset();

	// right ship
	m_ShipRight->setShipPartLevel(upgradeType::WEAPON, m_Data->buildTest.data[8]);
	m_ShipRight->setShipPartLevel(upgradeType::REACTOR, m_Data->buildTest.data[9]);
	m_ShipRight->setShipPartLevel(upgradeType::HULL, m_Data->buildTest.data[10]);
	m_ShipRight->setShipPartLevel(upgradeType::WINGS, m_Data->buildTest.data[11]);
	m_ShipRight->reset();

	// hp and dmg multiplier
	double multiplier_hp = 1 * m_Data->orbValue * m_Data->guild * (1 + m_Data->ascHp * 0.02) * (1 + m_Data->trophyHp * 0.01);
	double multiplier_damage = 1 * m_Data->orbValue * m_Data->guild * m_Data->guildPassive * (1 + m_Data->trophyDamage * 0.01) * (1 + m_Data->ascDamage * 0.02) * (1 + m_Data->legendary * 0.01);

	// find lcd
	uint64_t nok = lcd(m_ShipLeft->getSpeed(), m_ShipMiddle->getSpeed(), m_ShipRight->getSpeed(), m_ShipAI->getSpeed());

	// NOTE now must calculate new stats for this battle

	// left ship
	m_ShipLeft->m_Data.damage = m_ShipLeft->getDamage() * multiplier_damage;
	m_ShipLeft->m_Data.hp = m_ShipLeft->getHP() * multiplier_hp;
	m_ShipLeft->m_Data.hpMax = m_ShipLeft->m_Data.hp;
	m_ShipLeft->m_Data.shield = m_ShipLeft->getShield();
	m_ShipLeft->m_Data.shieldAbsorption = m_ShipLeft->getShieldAbsorption();
	m_ShipLeft->setBattleTimer(nok / m_ShipLeft->getSpeed());

	// middle ship
	m_ShipMiddle->m_Data.damage = m_ShipMiddle->getDamage() * multiplier_damage;
	m_ShipMiddle->m_Data.hp = m_ShipMiddle->getHP() * multiplier_hp;
	m_ShipMiddle->m_Data.hpMax = m_ShipMiddle->m_Data.hp;
	m_ShipMiddle->m_Data.shield = m_ShipMiddle->getShield();
	m_ShipMiddle->m_Data.shieldAbsorption = m_ShipMiddle->getShieldAbsorption();
	m_ShipMiddle->setBattleTimer(nok / m_ShipMiddle->getSpeed());

	// right ship
	m_ShipRight->m_Data.damage = m_ShipRight->getDamage() * multiplier_damage;
	m_ShipRight->m_Data.hp = m_ShipRight->getHP() * multiplier_hp;
	m_ShipRight->m_Data.hpMax = m_ShipRight->m_Data.hp;
	m_ShipRight->m_Data.shield = m_ShipRight->getShield();
	m_ShipRight->m_Data.shieldAbsorption = m_ShipRight->getShieldAbsorption();
	m_ShipRight->setBattleTimer(nok / m_ShipRight->getSpeed());

	// AI ship
	m_ShipAI->m_Data.damage = m_ShipAI->getDamage();
	m_ShipAI->m_Data.hp = m_ShipAI->getHP();
	m_ShipAI->m_Data.hpMax = m_ShipAI->getHP();
	m_ShipAI->m_Data.shield = m_ShipAI->getShield();
	m_ShipAI->m_Data.shieldAbsorption = m_ShipAI->getShieldAbsorption();
	m_ShipAI->setBattleTimer(nok / m_ShipAI->getSpeed());

	// set drone effects

	for(auto i = 0; i < 5; ++i) {
		switch(m_Drones[i]->getType()) {
			case droneType::CONTROLLER:
				// ignore this type of drone
				break;

			case droneType::DEAD:
				m_ShipLeft->m_Data.deadly = m_Drones[i]->getValue();
				m_ShipMiddle->m_Data.deadly = m_Drones[i]->getValue();
				m_ShipRight->m_Data.deadly = m_Drones[i]->getValue();
				break;

			case droneType::LEECH:
				m_ShipLeft->m_Data.leech += m_Drones[i]->getValue();
				m_ShipMiddle->m_Data.leech += m_Drones[i]->getValue();
				m_ShipRight->m_Data.leech += m_Drones[i]->getValue();
				break;

			case droneType::REFLECTOR:
				m_ShipLeft->m_Data.reflection += m_Drones[i]->getValue();
				m_ShipMiddle->m_Data.reflection += m_Drones[i]->getValue();
				m_ShipRight->m_Data.reflection += m_Drones[i]->getValue();
				break;

			case droneType::REGEN:
				m_ShipLeft->m_Data.regeneration += m_Drones[i]->getValue();
				m_ShipMiddle->m_Data.regeneration += m_Drones[i]->getValue();
				m_ShipRight->m_Data.regeneration += m_Drones[i]->getValue();
				break;

			case droneType::SHIELD_ABSORBTION:
				m_ShipLeft->m_Data.shieldAbsorption += m_Drones[i]->getValue();
				m_ShipMiddle->m_Data.shieldAbsorption += m_Drones[i]->getValue();
				m_ShipRight->m_Data.shieldAbsorption += m_Drones[i]->getValue();
				break;

			case droneType::SHIELD_PENETRATION:
				m_ShipLeft->m_Data.shieldPenetration += m_Drones[i]->getValue();
				m_ShipMiddle->m_Data.shieldPenetration += m_Drones[i]->getValue();
				m_ShipRight->m_Data.shieldPenetration += m_Drones[i]->getValue();
				break;
		}
	}
}

bool Arena::run() {
	bool win = false;
	// timers
	uint64_t timers[4];
	timers[0] = m_ShipLeft->getBattleTimer();
	// NOTE middle ship attack first and have 1.5 second delay
	timers[1] = m_ShipMiddle->getBattleTimer() * 1.5;
	timers[2] = m_ShipRight->getBattleTimer();
	timers[3] = m_ShipAI->getBattleTimer();


	bool end = false;
	turn(&m_ShipMiddle->m_Data, &m_ShipAI->m_Data);
	uint64_t min = 0;

	do {
		// left ship attack
		if(timers[0] == 0) {
			timers[0] = m_ShipLeft->getBattleTimer();

			if(m_ShipLeft->m_Data.alive) {
				turn(&m_ShipLeft->m_Data, &m_ShipAI->m_Data);
			}
		}

		// middle ship attack
		if(timers[1] == 0) {
			timers[1] = m_ShipMiddle->getBattleTimer();

			if(m_ShipMiddle->m_Data.alive) {
				turn(&m_ShipMiddle->m_Data, &m_ShipAI->m_Data);
			}
		}

		// right ship attack
		if(timers[2] == 0) {
			timers[2] = m_ShipRight->getBattleTimer();

			if(m_ShipRight->m_Data.alive) {
				turn(&m_ShipRight->m_Data, &m_ShipAI->m_Data);
			}
		}

		// ai ship attack
		if(timers[3] == 0) {
			timers[3] = m_ShipAI->getBattleTimer();

			if(m_ShipAI->m_Data.alive) {
				// NOTE first trying attack middle ship, then left ship, then right ship
				if(m_ShipMiddle->m_Data.alive) {
					turn(&m_ShipAI->m_Data, &m_ShipMiddle->m_Data);
				} else if(m_ShipLeft->m_Data.alive) {
					turn(&m_ShipAI->m_Data, &m_ShipLeft->m_Data);
				} else if(m_ShipRight->m_Data.alive) {
					turn(&m_ShipAI->m_Data, &m_ShipRight->m_Data);
				} else {
					end = true;
				}
			} else {
				// TODO check it in real fight
				// NOTE we can get draw in situation like this.
				// {"baselineMilitary":430,"baselineResources":[[1675876],[2787030],[1116202]],"bonuses":[[45],[7],[3.9250000000000007],[1.12],[50],[10],[10],[92],[85]],"drones":[[25],[25],[25],[25],[25]],"fifthDrone":"Leech","fourthDrone":"Regen","onhandResources":[[18826],[315846]],"orbProgress":0,"ship1":[[0],[0],[68],[0]],"ship2":[[35],[0],[62],[0]],"ship3":[[101],[0],[75],[100]],"testDrones":[[25],[25],[25],[25],[25]],"testFifthDrone":"Leech","testFourthDrone":"Regen","testMilitaryChallenge":470,"testShip1":[[0],[0],[80],[0]],"testShip2":[[35],[0],[64],[0]],"testShip3":[[105],[3],[82],[105]]}
				// TL;DR winner only if one of players' ship alive!
				if(m_ShipRight->m_Data.alive || m_ShipLeft->m_Data.alive || m_ShipMiddle->m_Data.alive) {
					win = true;
				}

				end = true;
			}
		}

		// NOTE sometimes 2 or more ships attack in same timer, check who died after all attack done
		m_ShipLeft->m_Data.alive = m_ShipLeft->m_Data.hp > 0;
		m_ShipMiddle->m_Data.alive = m_ShipMiddle->m_Data.hp > 0;
		m_ShipRight->m_Data.alive = m_ShipRight->m_Data.hp > 0;
		m_ShipAI->m_Data.alive = m_ShipAI->m_Data.hp > 0;

		// Sometimes AI ship can attack and died from reflection and next turn is not AI ship
		// but first we must ensure what someone alive
		if(!m_ShipAI->m_Data.alive) {
			win = m_ShipLeft->m_Data.alive || m_ShipRight->m_Data.alive || m_ShipMiddle->m_Data.alive;
			end = true;
		}

		// find which turn next
		min = timers[3] < timers[2] ? timers[3] : timers[2];

		if(timers[0] < min) {
			min = timers[0];
		}

		if(timers[1] < min) {
			min = timers[1];
		}

		timers[0] -= min;
		timers[1] -= min;
		timers[2] -= min;
		timers[3] -= min;
	} while(!end);

	return win;
}

void Arena::turn(ShipData * attacker, ShipData * victim) {
	/**
	 * NOTE Памятка по механике эффектов
	 * Нанесенный урон не учитывается хп врага, даже если оно ушло в минус.
	 * Отражение работает со всего нанесенного урона(щит+хп)
	 * Пробив щита не учитывается при отражении урона, учитывается только поглощение
	 * Вампиризм работает только с урона по хп, урон по щиту игнорируется
	 * Реген работает всегда в твой ход
	 * Здоровье корабля не может превысить максимум
	 */

	if(!victim->alive) {
		// TODO need this?
		throw;
		return;
	}

	// NOTE deadly increase damage not applied to AI
	double deadly_bonus = 1;

	if(attacker != &m_ShipAI->m_Data) {
		deadly_bonus += (m_ShipMiddle->m_Data.alive ? 0 : m_ShipMiddle->m_Data.deadly) + (m_ShipLeft->m_Data.alive ? 0 : m_ShipLeft->m_Data.deadly);
	}

	// how many damage we did
	double dmg = attacker->damage * deadly_bonus;
	double dmgDone = 0;
	// how many dmg can absorb victim's shield
	double dmg_to_armor_max = victim->shield * victim->shieldAbsorption / attacker->shieldPenetration;
	double leech_hp = 0;

	if(dmg > dmg_to_armor_max) {
		// victim's shield break
		dmgDone += victim->shield;
		dmg -= dmg_to_armor_max;
		dmgDone += dmg;
		victim->shield = 0;
		// other damage directly to hp!
		victim->hp -= dmg;

		//  leech
		leech_hp = dmg * attacker->leech;
	} else {
		// shield absorb our damage, no leech, only regen!
		victim->shield -= dmg * attacker->shieldPenetration / victim->shieldAbsorption;
	}

	// reflection
	if(victim->reflection > 0) {
		double dmg_reflect = dmgDone * victim->reflection;
		// NOTE reflected dmg not affected by shield penetration
		double dmg_reflect_to_armor_max = attacker->shield * attacker->shieldAbsorption;

		if(dmg_reflect > dmg_reflect_to_armor_max) {
			attacker->shield = 0;
			dmg_reflect -= attacker->shield;
			attacker->hp -= dmg_reflect;
		} else {
			attacker->shield -= dmg_reflect / attacker->shieldAbsorption;
		}
	}

	// regeration and leech
	attacker->hp += leech_hp;
	attacker->hp += attacker->hpMax * attacker->regeneration;

	// hp can't be above maximum
	if(attacker->hp > attacker->hpMax) {
		attacker->hp = attacker->hpMax;
	}
}

// helpers

uint32_t Arena::gcd(uint32_t a, uint32_t b) {
	while(b != 0) {
		uint32_t t = a % b;
		a = b;
		b = t;
	}

	return a;
}

uint64_t Arena::lcd(const uint32_t & a, const uint32_t & b) {
	return (uint64_t)a * b / gcd(a, b);
}

uint64_t Arena::lcd(const uint32_t & a, const uint32_t & b, const uint32_t & c, const uint32_t & d) {
	return lcd(a, lcd(b, lcd(c, d)));
}
